// Copyright Epic Games, Inc. All Rights Reserved.

#pragma once

#include "GameplayAbilitySpecHandle.h" // 游戏能力规格句柄
#include "GameplayTagContainer.h" // 游戏标签容器
#include "Subsystems/WorldSubsystem.h" // 世界子系统

#include "LyraGamePhaseSubsystem.generated.h"

template <typename T> class TSubclassOf; // 模板类声明

class ULyraGamePhaseAbility; // 前向声明
class UObject; // 前向声明
struct FFrame; // 前向声明
struct FGameplayAbilitySpecHandle; // 前向声明

// 动态委托：当游戏阶段结束时调用，参数为阶段能力对象。
DECLARE_DYNAMIC_DELEGATE_OneParam(FLyraGamePhaseDynamicDelegate, const ULyraGamePhaseAbility*, Phase);
// 委托：当游戏阶段结束时调用，参数为阶段能力对象。
DECLARE_DELEGATE_OneParam(FLyraGamePhaseDelegate, const ULyraGamePhaseAbility* Phase);

// 动态委托：当游戏阶段标签相关事件发生时调用，参数为阶段标签。
DECLARE_DYNAMIC_DELEGATE_OneParam(FLyraGamePhaseTagDynamicDelegate, const FGameplayTag&, PhaseTag);
// 委托：当游戏阶段标签相关事件发生时调用，参数为阶段标签。
DECLARE_DELEGATE_OneParam(FLyraGamePhaseTagDelegate, const FGameplayTag& PhaseTag);

// 阶段标签匹配类型的枚举
UENUM(BlueprintType)
enum class EPhaseTagMatchType : uint8
{
	// 精确匹配将只接收完全相同频道的消息（例如，注册 "A.B" 将匹配广播 A.B 但不匹配 A.B.C）
	ExactMatch, // 精确匹配
	// 部分匹配将接收同一频道根目录下的任何消息（例如，注册 "A.B" 将匹配广播 A.B 以及 A.B.C）
	PartialMatch // 部分匹配
};

/**
 * 用于管理Lyra游戏阶段的子系统，使用游戏标签以嵌套方式，允许父阶段和子阶段同时活跃，但不允许同级阶段同时活跃。
 * 示例：Game.Playing 和 Game.Playing.WarmUp 可以共存，但 Game.Playing 和 Game.ShowingScore 不能。
 * 当一个新的阶段开始时，任何不是其祖先的活跃阶段将被结束。
 * 示例：如果 Game.Playing 和 Game.Playing.CaptureTheFlag 在 Game.Playing.PostGame 开始时是活跃的，
 *     Game.Playing 将保持活跃，而 Game.Playing.CaptureTheFlag 将结束。
 */
UCLASS()
class ULyraGamePhaseSubsystem : public UWorldSubsystem
{
	GENERATED_BODY()

public:
	ULyraGamePhaseSubsystem(); // 构造函数

	virtual void PostInitialize() override; // 后初始化函数

	virtual bool ShouldCreateSubsystem(UObject* Outer) const override; // 判断是否应该创建子系统

	/**
	 * 启动一个游戏阶段。
	 * @param PhaseAbility 阶段能力类。
	 * @param PhaseEndedCallback 阶段结束时的回调委托。
	 */
	void StartPhase(TSubclassOf<ULyraGamePhaseAbility> PhaseAbility, FLyraGamePhaseDelegate PhaseEndedCallback = FLyraGamePhaseDelegate());

	//TODO 返回一个句柄以便人们可以删除这些观察者。它们会一直增长直到世界重置。
	//TODO 我们是否应该定期清理这些观察者？即使有句柄，也不是每个人都会正确地取消挂钩。
	/**
	 * 注册一个观察者，当指定阶段开始或已经活跃时触发回调。
	 * @param PhaseTag 要观察的阶段标签。
	 * @param MatchType 标签匹配类型。
	 * @param WhenPhaseActive 阶段活跃时的回调委托。
	 */
	void WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseActive);
	/**
	 * 注册一个观察者，当指定阶段结束时触发回调。
	 * @param PhaseTag 要观察的阶段标签。
	 * @param MatchType 标签匹配类型。
	 * @param WhenPhaseEnd 阶段结束时的回调委托。
	 */
	void WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseEnd);

	/**
	 * 检查指定阶段标签是否当前处于活跃状态。
	 * @param PhaseTag 要检查的阶段标签。
	 * @return 如果阶段活跃则返回true，否则返回false。
	 */
	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintPure = false, meta = (AutoCreateRefTerm = "PhaseTag"))
	bool IsPhaseActive(const FGameplayTag& PhaseTag) const;

protected:
	virtual bool DoesSupportWorldType(const EWorldType::Type WorldType) const override; // 判断是否支持指定的世界类型

	/**
	 * 蓝图可调用的启动阶段函数。
	 * @param Phase 阶段能力类。
	 * @param PhaseEnded 阶段结束时的动态委托。
	 */
	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName="Start Phase", AutoCreateRefTerm = "PhaseEnded"))
	void K2_StartPhase(TSubclassOf<ULyraGamePhaseAbility> Phase, const FLyraGamePhaseDynamicDelegate& PhaseEnded);

	/**
	 * 蓝图可调用的注册阶段开始观察者函数。
	 * @param PhaseTag 要观察的阶段标签。
	 * @param MatchType 标签匹配类型。
	 * @param WhenPhaseActive 阶段活跃时的动态委托。
	 */
	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName = "When Phase Starts or Is Active", AutoCreateRefTerm = "WhenPhaseActive"))
	void K2_WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseActive);

	/**
	 * 蓝图可调用的注册阶段结束观察者函数。
	 * @param PhaseTag 要观察的阶段标签。
	 * @param MatchType 标签匹配类型。
	 * @param WhenPhaseEnd 阶段结束时的动态委托。
	 */
	UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName = "When Phase Ends", AutoCreateRefTerm = "WhenPhaseEnd"))
	void K2_WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseEnd);

	/**
	 * 当阶段开始时由阶段能力调用。
	 * @param PhaseAbility 开始阶段的阶段能力对象。
	 * @param PhaseAbilityHandle 阶段能力的规格句柄。
	 */
	void OnBeginPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle);
	/**
	 * 当阶段结束时由阶段能力调用。
	 * @param PhaseAbility 结束阶段的阶段能力对象。
	 * @param PhaseAbilityHandle 阶段能力的规格句柄。
	 */
	void OnEndPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle);

private:
	// 游戏阶段条目结构，存储活跃阶段的信息。
	struct FLyraGamePhaseEntry
	{
	public:
		FGameplayTag PhaseTag; // 阶段标签
		FLyraGamePhaseDelegate PhaseEndedCallback; // 阶段结束回调委托
	};

	TMap<FGameplayAbilitySpecHandle, FLyraGamePhaseEntry> ActivePhaseMap; // 活跃阶段映射，键为能力句柄，值为阶段条目

	// 阶段观察者结构，用于注册阶段开始或结束的回调。
	struct FPhaseObserver
	{
	public:
		/**
		 * 判断观察者是否与给定的阶段标签匹配。
		 * @param ComparePhaseTag 要比较的阶段标签。
		 * @return 如果匹配则返回true，否则返回false。
		 */
		bool IsMatch(const FGameplayTag& ComparePhaseTag) const;
	
		FGameplayTag PhaseTag; // 观察的阶段标签
		EPhaseTagMatchType MatchType = EPhaseTagMatchType::ExactMatch; // 匹配类型，默认为精确匹配
		FLyraGamePhaseTagDelegate PhaseCallback; // 阶段回调委托
	};

	TArray<FPhaseObserver> PhaseStartObservers; // 阶段开始观察者数组
	TArray<FPhaseObserver> PhaseEndObservers; // 阶段结束观察者数组

	friend class ULyraGamePhaseAbility; // 友元类声明，允许ULyraGamePhaseAbility访问私有成员
};